package facades;

import java.sql.Date;
import java.util.ArrayList;

import common.Result;

import hitExceptions.InvalidDataException;
import hitExceptions.InvalidOperationException;
import coreModel.HIT;
import coreModel.Item;
import coreModel.ItemManager;
import coreModel.ProductContainer;
import coreModel.ProductManager;
import coreModel.StorageUnitManager;
import dataPersistence.TransactionManager;
import dataPersistence.dao.IItemDAO;
import dataPersistence.dto.ItemDTO;

/** A class to be a middleman between the gui and the core model. It represents  
 * much of the functionality of the Items (removed or not removed) of the model 
 * to the gui. It accepts core model objects as parameters, calls functions in 
 * the core model, and notifies its observers in the gui.
 */
public class ItemFacade extends java.util.Observable 
{
	final static NotificationEnum defaultN = NotificationEnum.ITEM_CHANGED; 
	private static ItemFacade instance;
	private static ItemManager manager = HIT.getInstance().getItemManager();
	private static ProductManager productManager = HIT.getInstance().getProductManager();
	private static StorageUnitManager storageManager = HIT.getInstance().getStorageUnitManager();


	/**
	 * Constructor, made private because this class is a singleton
	 */
	private ItemFacade()
	{
		//empty
	}

	/**
	 * Returns the instance of the itemFacade
	 * @return instance of the item facade
	 */
	public static ItemFacade getInstance()
	{
		if(instance==null)
		{
			instance= new ItemFacade();
		}
		return instance;
	}

	/** Requests that an item be added to the model in the appropriate container
	 *  and with the appropriate product.
	 * 
	 * @param newItem The item to add.
	 */
	public void addItem(Item newItem)
	{
		Result result = new Result();
		try 
		{
			TransactionManager.begin();
			result .setStatus(manager.addItem(newItem));
		}
		catch (InvalidOperationException e) 
		{
			System.err.println("Error in ItemFacade::addItem. "
					+ "Exception in ItemManager::addItem() was not expected to occur: "
					+ "\n\t" + e);
		}
		finally
		{
			if(!HIT.getInstance().isLoadingFromDataBase())
			TransactionManager.end(result.getStatus());
		}
		
//		dataPersistence(newItem, new DAORunnable() 
//				{public Result run(IItemDAO dao, ItemDTO dto)
//				{return dao.add(dto);}
//				});
		
		
		
		
		this.setChanged();
		notifyObservers(new Notification(newItem,defaultN,NotificationEnum.ADD));
		this.clearChanged();
	}

	/** Requests that the entry date of an item in the model be edited.
	 * 
	 * @param modifiedItem An object containing the new data for the item.
	 */
	public void editItem(Item modifiedItem) throws InvalidDataException
	{
		//copy the modified item's entry date into the item in the model
		Item itemRef = manager.getItem(modifiedItem.getBarcode());
		itemRef.setEntryDate(modifiedItem.getEntryDate());
		
		dataPersistence(modifiedItem, new DAORunnable() 
				{public Result run(IItemDAO dao, ItemDTO dto)
				{return dao.update(dto);}
				});
		
		this.setChanged();
		notifyObservers(new Notification(modifiedItem, defaultN,NotificationEnum.EDIT));
		this.clearChanged();
	}

	/** Requests that a item be moved to a specified container in the model.
	 * 
	 * @param item An object containing the data to look for and move.
	 * @param newContainer The container into which to move the item.
	 * @throws InvalidOperationException When the container isn't in the system.
	 */
	public void transferItem(Item item, ProductContainer newContainer) 
	throws InvalidOperationException
	{
		if(!HIT.getInstance().getProductManager().
				containsProductInContainer(item.getProduct(),newContainer))
		{
			HIT.getInstance().getProductManager().
			addProductToContainer(item.getProduct(),newContainer);

		}
		Item itemRef = manager.getItem(item.getBarcode());
		manager.moveItem(itemRef, newContainer);

		dataPersistence(itemRef, new DAORunnable() 
				{public Result run(IItemDAO dao, ItemDTO dto)
				{return dao.update(dto);}
				});
		
		this.setChanged();
		notifyObservers(new Notification(item,defaultN,NotificationEnum.TRANSFER));
		this.clearChanged();
	}

	/** Requests that a item be removed from the system and placed in removedItems.
	 * 
	 * @param item An object containing the data to look for and remove.
	 */
	public void removeItem(Item item)
	{
		manager.removeItem(item.getBarcode(), true, false);
		
		dataPersistence(item, new DAORunnable() 
				{public Result run(IItemDAO dao, ItemDTO dto)
				{
					dto.setExitDate(new java.util.Date());
					return dao.update(dto);
				}
				});
	
		
		this.setChanged();
		notifyObservers(new Notification(item,defaultN,NotificationEnum.REMOVE));
		this.clearChanged();
	}

	/** Requests that a item be removed from the system and NOT placed in removedItems.
	 * 
	 * @param item The item to look for and remove.
	 */
	public void unaddItem(Item item)
	{
		manager.removeItem(item.getBarcode(), false, false);

		dataPersistence(item, new DAORunnable() 
				{public Result run(IItemDAO dao, ItemDTO dto)
				{return dao.delete(dto);}
				});
		
		this.setChanged();
		notifyObservers(new Notification(item,defaultN,NotificationEnum.REMOVE));
		this.clearChanged();
	}
	
	/** Requests that an item be removed from the "removed items history"
	 *  and be replaced in its former container.
	 * 
	 * @param item The item to look for and replace.
	 */
	public void replaceItem(Item item) 
	{
		//remove the item from the removed items history
		HIT.getInstance().getItemHistory().removeItem(item);
		
		//add the item back to its former container
		try 
		{
			manager.addItem(item);
		}
		catch (InvalidOperationException e) 
		{
			System.err.println("Error in ItemFacade::replaceItem. "
					+ "Exception in ItemManager::addItem() was not expected to occur: "
					+ "\n\t" + e);
		}
		
		dataPersistence(item, new DAORunnable() 
				{public Result run(IItemDAO dao, ItemDTO dto)
				{return dao.update(dto);}
				});
		
		this.setChanged();
		notifyObservers(new Notification(item,defaultN,NotificationEnum.ADD)); //hack'd
		this.clearChanged();
	}

	/** Requests that a list of items be added to the model in the appropriate container(s)
	 *  and with the appropriate product(s).
	 * 
	 * @param itemList A list of items to add.
	 */
	public void addItems(ArrayList<Item> itemList)
	{
		if(itemList.isEmpty())
			return;
		
		//begin transaction
		Result result = new Result();
		result = TransactionManager.begin();
		if(!result.getStatus()) System.err.println(result.getMessage());
		IItemDAO dao = HIT.getInstance().getFactory().getItemDAO();
		
		for(Item item :itemList)
		{
			try 
			{
				manager.addItem(item);
			}
			catch (InvalidOperationException e) 
			{
				System.err.println("Error in ItemFacade::addItem. "
						+ "Exception in ItemManager::addItem() was not expected to occur: "
						+ "\n\t" + e);
			}

			//data persistence
			if (!HIT.getInstance().isLoadingFromDataBase())
			{
				ItemDTO dto = new ItemDTO(item);
				result = dao.update(dto);
				if(!result.getStatus()) System.err.println(result.getMessage());
			}
			
		}
		
		//end transaction
		TransactionManager.end(result.getStatus());
		
		this.setChanged();
		notifyObservers(new Notification(itemList.get(0),defaultN,NotificationEnum.ADD));
		this.clearChanged();
		
	}

	/** Requests that a list of items be removed from the system and NOT placed in removedItems.
	 * 
	 * @param itemList A list of the items to unadd.
	 */
	public void unaddItems(ArrayList<Item> itemList) 
	{
		if(itemList.isEmpty())
			return;
		
		//begin transaction
		Result result = new Result();
		result = TransactionManager.begin();
		if(!result.getStatus()) System.err.println(result.getMessage());
		IItemDAO dao = HIT.getInstance().getFactory().getItemDAO();
		
		for(Item item :itemList)
		{
			//changed to false so that non added items don't get put in the history
			manager.removeItem(item.getBarcode(), false, false);

			//data persistence
			if (!HIT.getInstance().isLoadingFromDataBase())
			{
				ItemDTO dto = new ItemDTO(item);
				result = dao.delete(dto);
				if(!result.getStatus()) System.err.println(result.getMessage());
			}
		}

		//end transaction
		TransactionManager.end(result.getStatus());
		
		this.setChanged();
		notifyObservers(new Notification(itemList.get(0),defaultN,NotificationEnum.REMOVE));
		this.clearChanged();
	}
	
	//this function consolidates code so that changes to data persistence in this class 
	// will only need doing in three functions: addItems(), unaddItems(), and this one. 
	private void dataPersistence(Item item, DAORunnable runnable)
	{

		//data persistence
		Result result = new Result();
		result = TransactionManager.begin();
		if(!result.getStatus()) System.err.println(result.getMessage());
		if (!HIT.getInstance().isLoadingFromDataBase())
		{
			IItemDAO dao = HIT.getInstance().getFactory().getItemDAO();
			ItemDTO dto = new ItemDTO(item);
			result = runnable.run(dao,dto);
			if(!result.getStatus()) System.err.println(result.getMessage());
		}
		TransactionManager.end(result.getStatus());
		
	}
	
	//helper interface to dataPersistence()
	private interface DAORunnable
	{
		public Result run(IItemDAO dao, ItemDTO dto);
	}

}
